package diplomovka.graphCreator;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.LocalVariable;
import org.jdom.Attribute;
import org.jdom.Element;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;

import diplomovka.graphCreator.modelParts.MAnnotation;
import diplomovka.graphCreator.modelParts.MClass;
import diplomovka.graphCreator.modelParts.MException;
import diplomovka.graphCreator.modelParts.MField;
import diplomovka.graphCreator.modelParts.MInterface;
import diplomovka.graphCreator.modelParts.MMethod;
import diplomovka.graphCreator.modelParts.Type;

public class ClazzCreator implements ModelPartCreator {

	private Map<String, List<String>> webServicesWithConfigs = null;
	@Override
	public boolean canHandle(Class loadedClass) {
		ExceptionCreator creator = new ExceptionCreator();
		boolean bol = loadedClass.isAnnotation() || loadedClass.isArray()
				|| loadedClass.isEnum() || loadedClass.isInterface()
				|| loadedClass.isPrimitive() || creator.canHandle(loadedClass);
		return !bol;
	}

	@Override
	/**
	 * Creates Clazz instance from given class
	 * 
	 * @param loadedClass
	 * @return
	 */
	public Type createModelPart(Class loadedClass, JavaClass loadedByteCodes,
			Map<String, Element> configFiles) {
		MClass clazz = new MClass();
		// get name of class
		String name = loadedClass.getName();
		String simpleName = loadedClass.getSimpleName();
		clazz.setName(name);
		Package classPackage = loadedClass.getPackage();
		if (classPackage != null) {
			clazz.setPackageName(classPackage.getName());
		}

		SAXBuilder builder = new SAXBuilder();

		Element classElement = null;
		Set<String> configFilesName = configFiles.keySet();
		for (String configFileName : configFilesName) {
			if (configFileName.contains(simpleName)) {
				Element rootElement = configFiles.get(configFileName);
				List<Element> classElements = rootElement.getChildren("class");
				for (Element element : classElements) {
					String className = element.getAttributeValue("name");
					if (className.equals(name)) {
						classElement = element;
						break;
					}
				}
			}
		}

		// load annotations
		Annotation[] annotations = loadedClass.getDeclaredAnnotations();
		for (Annotation annotation : annotations) {
			Class annotationType = annotation.annotationType();
			MAnnotation mAnnotation = new MAnnotation();
			mAnnotation.setName(annotationType.toString());
			clazz.addAnnotation(mAnnotation);
		}

		// get type of class
		Class type = loadedClass.getClass();
		clazz.setType(type);

		// load fields of class
		Field[] fields = loadedClass.getDeclaredFields();
		for (Field field : fields) {
			MField mField = new MField();
			Annotation[] fieldAnnotations = field.getDeclaredAnnotations();
			for (Annotation fieldAnnotation : fieldAnnotations) {
				MAnnotation annotation = new MAnnotation();
				annotation.setName(fieldAnnotation.annotationType().toString());
				mField.addAnnotation(annotation);
			}

			String fieldName = field.getName();
			boolean multiplicityFromConfigFile = false;
			Element childrenElementRef = null;
			if (classElement != null) {
				List<Element> classChildrenElements = classElement
						.getChildren();
				int index = 0;
				while ((!multiplicityFromConfigFile)
						&& (index < classChildrenElements.size())) {
					Element childrenElement = classChildrenElements
							.get(index++);
					String childrenElementName = childrenElement
							.getAttributeValue("name");
					if (childrenElementName.equals(fieldName)) {
						childrenElementRef = childrenElement;
						String multiplicityType = childrenElement.getName();
						if ((!multiplicityType.equals("one-to-one"))
								&& (!multiplicityType.equals("many-to-one"))) {
							if (childrenElement.getChild("one-to-many") != null)
								multiplicityType = "one-to-many";
							if (childrenElement.getChild("many-to-many") != null)
								multiplicityType = "many-to-many";
						}
						if (multiplicityType.equals("one-to-one")) {
							multiplicityFromConfigFile = true;
							mField.setMaximalMultiplicity("1");
							mField.setMinimalMultiplicity("1");

							String propertyRefValue = childrenElement
									.getAttributeValue("property-ref");
							if (propertyRefValue == null) {

							} else {
								mField.setInverseName(propertyRefValue);
								mField.setInverseMaximalMultiplicity("");
								// mField.setInverseMinimalMultiplicity(inverseMinimalMultiplicity);
							}
						} else {
							if (multiplicityType.equals("many-to-one")) {
								multiplicityFromConfigFile = true;
								mField.setMaximalMultiplicity("1");
								String notNullAttribute = childrenElement
										.getAttributeValue("not-null");
								if (notNullAttribute == null)
									notNullAttribute = "false";
								if (Boolean.parseBoolean(notNullAttribute)) {
									mField.setMinimalMultiplicity("1");
								} else {
									mField.setMinimalMultiplicity("0");
								}
							} else {
								if (multiplicityType.equals("one-to-many")) {
									multiplicityFromConfigFile = true;
									mField.setMaximalMultiplicity("*");
									Element idElement = childrenElement
											.getChild("key");
									String notNullAttribute = idElement
											.getAttributeValue("not-null");
									if (notNullAttribute == null)
										notNullAttribute = "false";
									if (Boolean.parseBoolean(notNullAttribute)) {
										mField.setMinimalMultiplicity("1");
									} else {
										mField.setMinimalMultiplicity("0");
									}
								} else {
									if (multiplicityType.equals("many-to-many")) {
										multiplicityFromConfigFile = true;
										mField.setMaximalMultiplicity("*");
										Element idElement = childrenElement
												.getChild("id");
										if (idElement != null) {
											String notNullAttribute = idElement
													.getAttributeValue("not-null");
											if (notNullAttribute != null)
												notNullAttribute = "false";
											if (Boolean
													.parseBoolean(notNullAttribute)) {
												mField
														.setMinimalMultiplicity("1");
											} else {
												mField
														.setMinimalMultiplicity("0");
											}
										}
									}
								}
							}
						}
					}
				}
			}

			java.lang.reflect.Type fieldGenericType = field.getGenericType();
			Class fieldType = null;
			if (fieldGenericType instanceof ParameterizedType) {
				ParameterizedType parameterizedType = (ParameterizedType) fieldGenericType;
				java.lang.reflect.Type[] actualTypes = parameterizedType
						.getActualTypeArguments();
				if (actualTypes.length == 1) {
					fieldType = (Class) actualTypes[0];
					if (!multiplicityFromConfigFile)
						mField.setMaximalMultiplicity("*");
				}
			} else {
				fieldType = getFieldsName(childrenElementRef, field);
				if (!multiplicityFromConfigFile)
					mField.setMaximalMultiplicity("1");
			}

			mField.setType(doBoxingForClass(fieldType));

			int fieldModifiers = field.getModifiers();
			mField.setModifiers(fieldModifiers);
			if (isFieldStatic(field, childrenElementRef))
				mField.setStaticField(true);
			if (!isFieldNullable(childrenElementRef))
				mField.setNotNull(true);
			if (isFieldIdentifier(childrenElementRef))
				mField.setIdentifier(true);
			if (isFieldLazyLoaded(childrenElementRef))
				mField.setAggregation(true);
			if (isFieldEagerLoaded(childrenElementRef))
				mField.setComposition(true);
			if (! getFieldCascade(childrenElementRef).equals(""))
				mField.setCascade(getFieldCascade(childrenElementRef));

			mField.setName(fieldName);

			clazz.addField(mField);
		}

		Method[] methods = loadedClass.getDeclaredMethods();
		for (Method method : methods) {
			List<String> parameterNames = new ArrayList<String>();
			org.apache.bcel.classfile.Method methodByteCode = loadedByteCodes
					.getMethod(method);
			if (methodByteCode.getLocalVariableTable() != null) {
				LocalVariable[] lv = methodByteCode.getLocalVariableTable()
						.getLocalVariableTable();
				for (LocalVariable localVariable : lv) {
					String parameterName = localVariable.getName();
					parameterNames.add(parameterName);
				}
			}

			MMethod mMethod = new MMethod();

			String methodName = method.getName();
			mMethod.setName(methodName);

			Annotation[] methodAnnotations = method.getDeclaredAnnotations();
			for (Annotation annotation : methodAnnotations) {
				Class annotationType = annotation.annotationType();
				MAnnotation mAnnotation = new MAnnotation();
				mAnnotation.setType(annotationType);
				mMethod.addAnnotation(mAnnotation);
			}

			Class[] exceptions = method.getExceptionTypes();
			for (Class exception : exceptions) {
				MException mException = new MException();
				mException.setName(exception.getName());
				mMethod.addException(mException);
			}

			int modifiers = method.getModifiers();
			mMethod.setModifiers(modifiers);
			String modifiersString = Modifier.toString(modifiers);

			Class[] parameters = method.getParameterTypes();
			int index = 1;
			if (modifiersString.contains("static"))
				index = 0;

			for (Class parameter : parameters) {
				String paramName = parameterNames.get(index++);
				mMethod.addParameter(paramName, doBoxingForClass(parameter));
			}

			Class returnType = method.getReturnType();
			mMethod.setReturnType(doBoxingForClass(returnType));
			
			if (isMethodInWebServiceConfig(simpleName, methodName, configFiles))
				mMethod.setInWebServiceConfigFile(true);

			clazz.addMethod(mMethod);
		}

		int modifiers = loadedClass.getModifiers();
		clazz.setModifiers(modifiers);

		Class supertype = loadedClass.getSuperclass();
		if (supertype != null) {
			MClass superClass = new MClass();
			superClass.setName(supertype.toString());
			clazz.setSupertype(superClass);
		}

		Class[] interfaces = loadedClass.getInterfaces();
		for (Class classInterface : interfaces) {
			MInterface mInterface = new MInterface();
			mInterface.setName(classInterface.toString());
			clazz.addInterface(mInterface);
		}
		
		if (isWebServiceWithConfig(simpleName, configFiles))
			clazz.setHasWebServiceConfig(true);

		return clazz;
	}

	private Class doBoxingForClass(Class inputClass) {
		if (inputClass.equals(int.class)) {
			return Integer.class;
		}
		if (inputClass.equals(short.class)) {
			return Short.class;
		}
		if (inputClass.equals(long.class)) {
			return Long.class;
		}
		if (inputClass.equals(byte.class)) {
			return Byte.class;
		}
		if (inputClass.equals(float.class)) {
			return Float.class;
		}
		if (inputClass.equals(double.class)) {
			return Double.class;
		}
		if (inputClass.equals(char.class)) {
			return Character.class;
		}
		if (inputClass.equals(boolean.class)) {
			return Boolean.class;
		}

		return inputClass;
	}

	private boolean isFieldStatic(Field field, Element childrenElementRef) {
		int fieldModifiers = field.getModifiers();
		String modifierString = Modifier.toString(fieldModifiers);
		if (modifierString.contains("static"))
			return true;
		else {
			if (childrenElementRef != null) {
				String childrenElementName = childrenElementRef.getName();
				if (childrenElementName.equals("property")) {
					Attribute attribute = childrenElementRef
							.getAttribute("unique");
					if (attribute != null
							&& attribute.getValue().equals("true"))
						return true;
				}
			}
		}

		return false;
	}

	private boolean isFieldNullable(Element childrenElementRef) {
		if (childrenElementRef != null) {
			String childrenElementName = childrenElementRef.getName();
			if (childrenElementName.equals("property")) {
				Attribute attribute = childrenElementRef
						.getAttribute("not-null");
				if (attribute != null && attribute.getValue().equals("true"))
					return false;
			}
		}

		return true;
	}

	private boolean isFieldIdentifier(Element childrenElementRef) {
		if (childrenElementRef != null) {
			String childrenElementName = childrenElementRef.getName();
			if (childrenElementName.equals("id")) {
				return true;
			}
		}

		return false;
	}
	
	private String getFieldCascade(Element childrenElementRef) {
		if (childrenElementRef != null) {
			Attribute attribute = childrenElementRef.getAttribute("cascade");
			if (attribute != null) {
				return attribute.getValue();
			}
		}

		return "";
	}

	private boolean isFieldLazyLoaded(Element childrenElementRef) {
		if (childrenElementRef != null) {
			Attribute attribute = childrenElementRef.getAttribute("lazy");
			if (attribute != null && attribute.getValue().equals("proxy"))
				return true;
		}

		return false;
	}

	private boolean isFieldEagerLoaded(Element childrenElementRef) {
		if (childrenElementRef != null) {
			Attribute attribute = childrenElementRef.getAttribute("lazy");
			if (attribute != null && attribute.getValue().equals("false"))
				return true;
			if (attribute != null && attribute.getValue().equals("no-proxy"))
				return true;
		}

		return false;
	}

	private Class getFieldsName(Element childrenElementRef, Field field) {
		if (field.getType().getName().equals(Set.class.getName())) {
			List<Element> childrenList = childrenElementRef.getChildren();
			for (Element element : childrenList) {
				if (!element.getName().equals("key")
						&& element.getAttribute("class") != null) {
					try {
						return Class.forName(element.getAttributeValue("class"));
					} catch (ClassNotFoundException e) {
						e.printStackTrace();
					}
				}
			}
		}

		return field.getType();

	}
	
	private boolean isWebServiceWithConfig(String className, Map<String, Element> configFiles) {
		loadWebServicesWithConfigs(configFiles);
		return webServicesWithConfigs.keySet().contains(className);
	}
	
	private boolean isMethodInWebServiceConfig(String className, String methodName, Map<String, Element> configFiles) {
		loadWebServicesWithConfigs(configFiles);
		if (! isWebServiceWithConfig(className, configFiles))
			return false;
		
		List<String> servicesList = webServicesWithConfigs.get(className);
		return servicesList.contains(methodName.toLowerCase()) || servicesList.contains("*");
	}
	
	private void loadWebServicesWithConfigs(Map<String, Element> configFiles) {
		if (webServicesWithConfigs != null)
			return;
		
		webServicesWithConfigs = new HashMap<String, List<String>>();
		
		for (String configFileName : configFiles.keySet()) {
			if (configFileName.endsWith(".wsdl")) {
				Element rootElement = configFiles.get(configFileName);
				Element portTypeElement = rootElement.getChild("portType");
				List<Element> operationElementsList = portTypeElement.getChildren("operation");
				List<String> servicesList = new ArrayList<String>();
				for (Element operationElement : operationElementsList) {
					String name = operationElement.getAttributeValue("name");
					servicesList.add(name.toLowerCase());
				}
				webServicesWithConfigs.put(configFileName.substring(0, configFileName.length() - 5), servicesList);
			}
			if (configFileName.endsWith(".wsdd")) {
				Element rootElement = configFiles.get(configFileName);
				List<Element> serviceElementsList = rootElement.getChildren("service", Namespace.getNamespace("http://xml.apache.org/axis/wsdd/"));
				for (Element serviceElement : serviceElementsList) {
					List<Element> parameterElementsList = serviceElement.getChildren("parameter", Namespace.getNamespace("http://xml.apache.org/axis/wsdd/"));
					List<String> servicesList = null;
					String className = "";
					for (Element parameterElement : parameterElementsList) {
						if (parameterElement.getAttributeValue("name").equals("className")) {
							className = parameterElement.getAttributeValue("value");
							if (servicesList != null)
								webServicesWithConfigs.put(className.substring(className.lastIndexOf(".") >= 0 ? className.lastIndexOf(".") + 1 : 0), servicesList);							
						}
						if (parameterElement.getAttributeValue("name").equals("allowedMethods")) {
							String allowedMethods = parameterElement.getAttributeValue("value");
							String[] servicesNames = allowedMethods.split(",");
							servicesList = new ArrayList<String>();
							for (String serviceName : servicesNames) {
								servicesList.add(serviceName.toLowerCase());
							}
							if (! className.equals(""))
								webServicesWithConfigs.put(className.substring(className.lastIndexOf(".") >= 0 ? className.lastIndexOf(".") + 1 : 0), servicesList);							
						} 
					}
				}
			}
		}
	}
}
